#
# complex HTMl output - multi-page HTMl output with indexes and source code highlighting.
#
import sys
import shutil
import cppAnalyze
import os.path

from fileutils import getFileText
import pygments
from pygments import highlight
from pygments.lexers import CppLexer
from pygments.formatters import HtmlFormatter

#
# config class - arbitary config data is stored here:
class Config:
	pass

#
# THis class creates an HTM output file for the filename give in the class constructor.
#
# To use thisclss, create an instance of the class with the C/C++ filename. THis will be
# converted into a more HTML-friendly name.
#
# THen add test faulires one at a time. Once that's done, cqall write. THe write() method will return the
# path of the file written.

class resultFile:
	def __init__(self, path):
		self.path = path
		name = os.path.split(path)[1]
		self.name = self._getFriendlyName(name)
		self.failures = []

		# TODO - some of these need to come from user-defined configuration!
		# number of lines of context to give BEFORE the code failure:
		self.contextBefore = Config.failure_syntax_before
		# simmilar, but number of lines context to give AFTER code failure:
		self.contextAfter = Config.failure_syntax_after

	def _getFriendlyName(self,name):
		return name.replace(".","_")

	def addResult(self, testname, lineStart, lineEnd):
		self.failures.append((testname, int(lineStart), int(lineEnd)))

	def _writeHeader(self, file):
		file.write("<html><head><title>%s</title><style>%s</style></head><body>" % (self.name, self._getStyle()))
		file.write('<h1>Results for: %s</h1>' % (os.path.split(self.path)[1]))
		file.write('<p>What follows is a list of test failures found for this file. Each test failure shows the code concerned as well as the line number at which the test failed.</p>')
		
	def _writeFooter(self, file):
		file.write("</body></html>")

	def _writeResults(self, file):
		for testname, start,end in self.failures:
			# get the raw code string:
			lnStart = start - self.contextBefore
			lnCount = self.contextAfter + self.contextBefore + 1
			print "Getting lines %d+%d" % (lnStart, lnCount)
			codeStrRaw = getFileText(self.path, lnStart , lnCount )
			codeStrHighlighted = pygments.highlight(codeStrRaw, CppLexer(), HtmlFormatter(linenos='table',hl_lines=[start],linenostart=start - self.contextBefore + 1))
			file.write('''
			<div class="result">
				<p class="result">The following code failed the '%s' test on line <b>%d</b>:</p>
				%s
			</div>''' % (testname, start, codeStrHighlighted))

	def _getStyle(self):
		return HtmlFormatter().get_style_defs('.highlight') + '\ndiv.highlight { width: 100%; }\ntd.code { width: 100%;}\n'

	def write(self, destDir):
		fname = self.name + ".html"
		fullPath = os.path.join(destDir,fname)
		outFile = open(fullPath, "w")

		# write HTML header:
		self._writeHeader(outFile)
		# write results:
		self._writeResults(outFile)
		# write HTML footer:
		self._writeFooter(outFile)
		outFile.close()
		return fname
		



class CopyPlugin(cppAnalyze.Plugin):
	def __init__(self):
		cppAnalyze.Plugin.__init__(self)
		self.pluginName = 'complexhtml'
		self.pluginDescription = 'outputs a complex multi-page HTML report with source coe highlighting and CSS styling.'

	def getConfigSettings(self):
		return ('destDir,string',
				'create_alpha_index,bool,true',
				'create_path_index,bool,true',
				'failure_syntax_before,int,2',
				'failure_syntax_after,int,3',
				'show_full_src,bool,true')

	def processPluginOptions(self, argv):
		if not argv:
			print 'Invalid plugin options. Expecting --dstDir <path>.'
			return False

		if '--inputFile' in argv:
			inFileIndex = argv.index('--inputFile') + 1
			if inFileIndex >= len(argv):
				print 'Error: No input file was specified.'
				return False
			else:
				self.inputFile = argv[inFileIndex]
				return True
		else:
			return False

	def write_alpha_index(self, file):
		fname = os.path.join(Config.destDir,file)
		outfile = open(fname, "w")

		outfile.write('<html><head><title>Alphabetical Index</title></head>')
		outfile.write('<body>')
		outfile.write('<h1>Alphabetical index</h1>')

		files = self.resultsMap.keys()
		files.sort()
		
		firstChar = None
		listStarted = False
		for item in files:
			#print self.resultsMap[item]
			itemname = os.path.split(item)[1]
			if itemname[0] != firstChar:
				firstChar = itemname[0]
				if listStarted:
					outfile.write('</ul>')
					listStarted = False
				outfile.write('<h2>%s:</h2>' % (firstChar.upper()))
				outfile.write('<ul>')
				listStarted = True
			outfile.write('<li><a href="%s">%s</a></li>' % (self.resultsMap[item][-1],itemname))
		outfile.write('</body></html>')

	def write_path_index(self, file):
		fname = os.path.join(Config.destDir,file)
		outfile = open(fname, "w")

		outfile.write('<html><head><title>Path Index</title></head>')
		outfile.write('<body>')
		outfile.write('<h1>Path index</h1>')

		# need to process directory paths:
		paths = {}
		for item in self.resultsMap:
			path,file = os.path.split(item)
			if not paths.has_key(path):
				paths[path] = []
			paths[path].append((file, self.resultsMap[item][-1]))

		# trim this amount from the header strings:
		trimAmount = len(Config.destDir)

		listStarted = False
		for path in paths:
			if listStarted:
				outfile.write('</ul>')
				listStarted = False
			outfile.write('<h2>%s</h2>' % (path[trimAmount:]))
			for file in paths[path]:
				if not listStarted:
					outfile.write('</ul>')
					listStarted = True
				outfile.write('<li><a href="%s">%s</a></li>' % (file[1],file[0]))

		outfile.write('</body></html>')
	def makeStuffGoNow(self):
		# TODO - should come from config:
		Config.destDir = str(self.getSetting('destDir').toString())
		Config.create_alpha_index = bool(self.getSetting('create_alpha_index').toBool())
		Config.create_path_index = bool(self.getSetting('create_path_index').toBool())
		Config.failure_syntax_before = int(self.getSetting('failure_syntax_before').toInt()[0])
		Config.failure_syntax_after = int(self.getSetting('failure_syntax_after').toInt()[0])
		Config.show_full_src = bool(self.getSetting('show_full_src').toBool())

		print Config.failure_syntax_before, Config.failure_syntax_after
		print "Destination dir =", str(Config.destDir)
		
		if not Config.destDir:
			print 'Error: No destDir was specified in the config-file.'
			return -1

		# clean dest dir, then re-make it:
		shutil.rmtree(Config.destDir, True)
		os.makedirs(Config.destDir)



		#
		# Scan input file, store results in a memory map:
		# map filename -> (test, line)
		#
		self.resultsMap = {}

		infile = open(self.inputFile, "r")
		for line in infile:
			line = line.strip()
			parts = line.split("|")
			if len(parts) != 3:
				continue

			test, file, line = parts
			if not self.resultsMap.has_key(file):
				self.resultsMap[file] = []
			self.resultsMap[file].append([test,line])

		# write the individual results files to disk; remember filenames so we can link
		# to them at a later date.
		for file in self.resultsMap:
			fobj = resultFile(file)
			for test,line in self.resultsMap[file]:
				fobj.addResult(test,line,line)
			outFile = fobj.write(Config.destDir)
			self.resultsMap[file].append(outFile)
			print "Results saved in:",outFile

		
#		for item in self.resultsMap:
#			print item, self.resultsMap[item][-1]
			
		# now write the index:
		if Config.create_alpha_index:
			self.write_alpha_index("alpha_index.html")
			print "Alphabetical index written"
		if Config.create_path_index:
			self.write_path_index("path_index.html")
			print "Path index written"
		
		
if __name__ == '__main__':
        exit(CopyPlugin().runPlugin(sys.argv))

